﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows.Threading;
using System.ComponentModel;
using Microsoft.Win32;
using System.Diagnostics;
using System.Xml.Linq;

namespace DomainScanner
{
    public class DomainResult : INotifyPropertyChanged
    {
        private string m_domainName;
        private string m_resolvedIP;
        private bool m_tested;
        private bool m_successful;
        private bool m_interesting;

        public DomainResult(String domainName)
        {
            DomainName = domainName;
            ResolvedIP = "Queued...";
            Tested = false;
            Successful = false;
            Interesting = false;
        }

        public String DomainName
        {
            get
            {
                return m_domainName;
            }
            set
            {
                m_domainName = value;
                NotifyPropertyChanged("DomainName");
            }
        }

        public String ResolvedIP
        {
            get
            {
                return m_resolvedIP;
            }
            set
            {
                m_resolvedIP = value;
                NotifyPropertyChanged("ResolvedIP");
            }
        }

        public bool Tested
        {
            get
            {
                return m_tested;
            }
            set
            {
                m_tested = value;
                NotifyPropertyChanged("Tested");
            }
        }

        public bool Successful
        {
            get
            {
                return m_successful;
            }
            set
            {
                m_successful = value;
                NotifyPropertyChanged("Successful");
            }
        }

        public bool Interesting
        {
            get
            {
                return m_interesting;
            }
            set
            {
                m_interesting = value;
                NotifyPropertyChanged("Interesting");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }

    public class WordList
    {
        public WordList()
        {
            Words = new ObservableCollection<String>();
        }

        public ObservableCollection<String> Words {get; set;}
    }

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private bool CancelThreads { get; set; }
        private string m_ignoreIPs;
        public String Prefix { get; set; }
        public String Suffix { get; set; }
        public String WordDelimiter { get; set; }
        public int MinWords { get; set; }
        public ObservableCollection<DomainResult> DomainResults { get; set; }
        public ObservableCollection<WordList> WordLists { get; set; }
        private List<Thread> m_threadPool = new List<Thread>();
        private Dictionary<string, DomainResult> m_generatedDomains = 
            new Dictionary<string, DomainResult>();
        public ObservableCollection<DomainResult> PendingEvaluation { get; set; }
        public ObservableCollection<DomainResult> FlaggedResults { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            DomainResults = new ObservableCollection<DomainResult>();
            WordLists = new ObservableCollection<WordList>();
            PendingEvaluation = new ObservableCollection<DomainResult>();
            FlaggedResults = new ObservableCollection<DomainResult>();
            
            Prefix = "www.";
            Suffix = ".com";
            WordDelimiter = "";
            MinWords = 2;
            IgnoreIPs = "";

            if (!Load("Config.xml"))
            {
                WordLists.Add(new WordList());
            }

            ThreadStart start = new ThreadStart(delegate() { DoWork(this.Dispatcher); });
            for (int i = 0; i < 30; i++)
            {
                Thread thread = new Thread(start);
                thread.Start();
                m_threadPool.Add(thread);
            }
            
            DataContext = this;

            this.Closing += new CancelEventHandler(MainWindow_Closing);
        }

        void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            CancelThreads = true;

            Save("Config.xml");
        }

        void Save(string filename)
        {
            XDocument doc = new XDocument();
            XElement xroot = new XElement("DomainScannerConfig");

            xroot.SetAttributeValue("Prefix", Prefix);
            xroot.SetAttributeValue("Suffix", Suffix);
            xroot.SetAttributeValue("WordDelimiter", WordDelimiter);
            xroot.SetAttributeValue("MinWords", MinWords);
            xroot.SetAttributeValue("IgnoreIPs", IgnoreIPs);

            XElement xWordLists = new XElement("WordLists");
            foreach (WordList wl in WordLists)
            {
                XElement xlist = new XElement("WordList");
                xWordLists.Add(xlist);

                foreach (string word in wl.Words)
                {
                    xlist.Add(new XElement("Word",
                        new XAttribute("value", word)));
                }
            }
            xroot.Add(xWordLists);

            XElement xDomainResults = new XElement("DomainResults");
            foreach (DomainResult result in DomainResults)
            {
                XElement xresult = new XElement("DomainResult");
                xDomainResults.Add(xresult);

                xresult.SetAttributeValue("DomainName", result.DomainName);
                xresult.SetAttributeValue("ResolvedIP", result.ResolvedIP);
                xresult.SetAttributeValue("Tested", result.Tested);
                xresult.SetAttributeValue("Successful", result.Successful);
                xresult.SetAttributeValue("Interesting", result.Interesting);
            }
            xroot.Add(xDomainResults);

            doc.Add(xroot);
            doc.Save(filename);
        }

        bool Load(string filename)
        {
            XDocument xdoc = null;
            try
            {
                xdoc = XDocument.Load(filename);
            }
            catch (System.Exception){}
            if (xdoc == null)
                return false;

            XElement xroot = xdoc.Root;
            Prefix = (string)xroot.Attribute("Prefix");
            Suffix = (string)xroot.Attribute("Suffix");
            WordDelimiter = (string)xroot.Attribute("WordDelimiter");
            MinWords = (int)xroot.Attribute("MinWords");
            IgnoreIPs = (string)xroot.Attribute("IgnoreIPs");

            XElement xWordLists = xroot.Element("WordLists");
            foreach (XElement xlist in xWordLists.Elements())
            {
                WordList wl = new WordList();
                WordLists.Add(wl);

                foreach (XElement xword in xlist.Elements())
                {
                    wl.Words.Add((string)xword.Attribute("value"));
                }
            }

            XElement xDomainResults = xroot.Element("DomainResults");
            foreach (XElement xresult in xDomainResults.Elements())
            {
                string domainName = (string)xresult.Attribute("DomainName");
                DomainResult result = new DomainResult(domainName);
                if (m_generatedDomains.ContainsKey(domainName))
                    continue;
                DomainResults.Add(result);
                m_generatedDomains.Add(domainName, result);

                result.ResolvedIP = (string)xresult.Attribute("ResolvedIP");
                result.Tested = (bool)xresult.Attribute("Tested");
                result.Successful = (bool)xresult.Attribute("Successful");
                result.Interesting = (bool)xresult.Attribute("Interesting");

                if (!result.Tested)
                {
                    PendingEvaluation.Add(result);
                }
                if (result.Interesting)
                {
                    FlaggedResults.Add(result);
                }
                
            }

            return true;
        }

        public void OnAddWordList(Object sender, EventArgs e)
        {
            WordLists.Add(new WordList());
        }

        public void OnAddWordClick(Object sender, EventArgs e)
        {
            Button btn = sender as Button;
            TextBox tb = btn.DataContext as TextBox;
            WordList wl = tb.DataContext as WordList;
            String word = tb.Text.Trim();
            if (word.Length != 0 && !wl.Words.Contains(word))
            {
                wl.Words.Add(word);
                tb.Text = "";
            }
        }

        public void OnRemoveWordClick(Object sender, EventArgs e)
        {
            Button btn = sender as Button;
            Grid grid = btn.Parent as Grid;
            ListBox lb = grid.FindName("wordList") as ListBox;
            TextBox tb = btn.DataContext as TextBox;
            WordList wl = tb.DataContext as WordList;

            string word = lb.SelectedItem as string;
            if (word != null)
                wl.Words.Remove(word);
        }

        public void OnIgnoreIP(Object sender, EventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            DomainResult result = mi.DataContext as DomainResult;

            string ignorelist = IgnoreIPs;
            if (ignorelist != "")
                ignorelist += ",";
            IgnoreIPs = ignorelist + result.ResolvedIP;
        }

        public void OnRetryResolve(Object sender, EventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            DomainResult result = mi.DataContext as DomainResult;

            lock(PendingEvaluation)
            {
                if (!PendingEvaluation.Contains(result))
                    PendingEvaluation.Add(result);
            }
        }

        public String IgnoreIPs
        {
            get
            {
                return m_ignoreIPs;
            }
            set
            {
                m_ignoreIPs = value;
                NotifyPropertyChanged("IgnoreIPs");

                lock(FlaggedResults)
                {
                    List<DomainResult> ignore = new List<DomainResult>();
                    foreach (DomainResult result in FlaggedResults)
                    {
                        if (!IsInteresting(result))
                            ignore.Add(result);
                    }

                    foreach (DomainResult result in ignore)
                    {
                        ToggleInteresting(result);
                    }
                }
                
            }
        }

        private bool IsInteresting(DomainResult result)
        {
            string[] ignoreList = m_ignoreIPs.Split(',');
            foreach (string item in ignoreList)
            {
                string ip = item.Trim();
                if (ip != "")
                if (result.ResolvedIP.Contains(ip))
                    return false;
            }
            return true;
        }

        public void OnOpenSite(Object sender, EventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            DomainResult result = mi.DataContext as DomainResult;

            string key = @"htmlfile\shell\open\command";
            RegistryKey registryKey = Registry.ClassesRoot.OpenSubKey(key, false);
            // get default browser path
            string browserPath = ((string) registryKey.GetValue(null, null)).Split('"')[1];

            try
            {
                // launch default browser
                Process.Start(browserPath, result.DomainName);
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message);
            }
        }

        public void OnToggleInteresting(Object sender, EventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            DomainResult result = mi.DataContext as DomainResult;

            ToggleInteresting(result);
        }

        private void ToggleInteresting(DomainResult result)
        {
            lock (FlaggedResults)
            {
                if (FlaggedResults.Contains(result))
                {
                    FlaggedResults.Remove(result);
                    result.Interesting = false;
                }
                else
                {
                    FlaggedResults.Add(result);
                    result.Interesting = true;
                }
            }
        }

        public void OnClear(Object sender, EventArgs e)
        {
            m_generatedDomains.Clear();
            DomainResults.Clear();
            lock (PendingEvaluation)
            {
                PendingEvaluation.Clear();
            }
            FlaggedResults.Clear();
        }

        public void OnScan(Object sender, EventArgs e)
        {
            GenerateDomains(Prefix, 0);
        }

        public void GenerateDomains(String prefix, int listIndex)
        {
            if (listIndex < WordLists.Count)
            {
                if (listIndex == MinWords)
                {
                    AddDomain(prefix + Suffix);
                }

                WordList wl = WordLists[listIndex];
                foreach (String word in wl.Words)
                {
                    string domain = prefix;
                    if (listIndex != 0)
                        domain += WordDelimiter;
                    domain += word;
                    GenerateDomains(domain, listIndex + 1);
                }
            }
            else
            {
                AddDomain(prefix + Suffix);
            }
        }

        public void AddDomain(String domain)
        {
            if (!m_generatedDomains.ContainsKey(domain))
            {
                DomainResult result = new DomainResult(domain);
                DomainResults.Add(result);
                m_generatedDomains.Add(domain, result);
                lock (PendingEvaluation)
                {
                    PendingEvaluation.Add(result);
                }
            }
        }

        public void DoWork(Dispatcher disp)
        {
            while (!CancelThreads)
            {
                DomainResult result = null;

                disp.Invoke((Action)delegate()
                {
                    lock (PendingEvaluation)
                    {
                        if (PendingEvaluation.Count != 0)
                        {
                            result = PendingEvaluation[0];
                            PendingEvaluation.RemoveAt(0);
                        }
                    }
                });
                
                if (result != null)
                {
                    Evaluate(disp, result);
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
            
        }

        void Evaluate(Dispatcher disp, DomainResult result)
        {
            disp.Invoke((Action)delegate(){
                lock (result)
                {
                    result.ResolvedIP = "Resolving...";
                }
            });

            string resolution = "";
            try
            {
                System.Net.IPHostEntry host = System.Net.Dns.GetHostEntry(result.DomainName);
                foreach (string alias in host.Aliases)
                {
                    if (resolution != "")
                        resolution += ", ";
                    resolution += alias;
                }
                foreach (System.Net.IPAddress ip in host.AddressList)
                {
                    if (resolution != "")
                        resolution += ", ";
                    resolution += ip;
                }
            }
            catch (System.Exception){}

            disp.Invoke((Action)delegate()
            {
                lock(result)
                {
                    result.Successful = resolution != "";
                    result.ResolvedIP = resolution;
                    if (!result.Successful)
                    {
                        result.ResolvedIP = "Does not exist";
                    }
                    else 
                    {
                        if (IsInteresting(result))
                        {
                            lock (FlaggedResults)
                            {
                                FlaggedResults.Add(result);
                                result.Interesting = true;
                            }
                        }
                    }
                    result.Tested = true;
                }
            });
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}
